// 加密解密规则
const {encode, decode} = require('../base64')
// token的最长有效时长。单位毫秒。默认两个小时
const expires_in = 3600000 * 2
// 登录的最长时长。单位毫秒。默认是7天。当不满足expires_in时再判断expires_login
const expires_login = 3600000 * 24 * 7
// token内容之间的连接符
const TokenSymbol = '|+|'

/**
 * 验证未通过的错误信息
 * 需同步到前端
 * 700+，表示系统登录验证的错误
 * 800+，表示项目登录验证的错误
 */
const errorInfo = {
	0: {
		code: 0,
		message: '验证通过'
	},
	1: {
		code: 1,
		message: 'Token不存在或者不合法'
	},
	2: {
		code: 2,
		message: '当前应用没有权限'
	},
	3: {
		code: 3,
		message: '甲方未开放'
	},
	4: {
		code: 4,
		message: '项目未开放'
	},
	5: {
		code: 5,
		message: '级别不满足要求'
	},
	6: {
		code: 6,
		message: '没有权限'
	},
	7: {
		code: 7,
		message: '用户未开放'
	},
	8: {
		code: 8,
		message: '请求已过期'
	}, // 目前不抛出该错误
	9: {
		code: 9,
		message: '登录已过期'
	}, // 要求重新登录,
	10: {
		code: 10,
		message: '其它错误'
	},
	11: {
		code: 11,
		message: '会员不满足要求'
	}
}

// 解析token
const analysis = (token) => {
	return decode(token).split(TokenSymbol)
}

/**
 * 生成token。只有登录时，才直接生成的方法，其它场景都是调更新的方法
 * arr是一个以下多个元素的数组。依次如下
 * 1，登录时间
 * 2，应用代码
 * 3，环境代码
 * 4，用户id
 * 5，项目代码。以及项目前缀，用-隔开
 * 6，甲方代码
 * 7，级别代码
 * 8，权限代码。会有多项，是用|隔开的字符串
 * 9，会员级别
 * 10，设备id
 * 11，当前的时间戳
 */
const get = (arr) => {
	if (arr.length === 11) {
		return encode(arr.join(TokenSymbol))
	}
	return undefined
}

/**
 * token无法实现存储。token逻辑是：
 * 1，生成规则，总共十一项：
		 * 1，timestamp1：登录时间；
		 * 2，apply_code：应用代码；
		 * 3，test_code：环境代码。on-produce（线上正式环境）、test-produce(线上测试环境)、on-develop（本地正式环境）、test-develop(本地测试环境)
		 * 4，user_id：用户id。系统用户id或者子项目用户id
		 * 5，target_code：项目代码。系统项目代码或者子项目代码，以及项目前缀，用-隔开
		 * 6，partya_code：甲方代码。系统甲方代码或者子项目甲方代码
		 * 7，rank_code：级别代码。系统级别代码或者子项目级别代码
		 * 8，au_code：权限代码。系统权限代码或者子项目权限代码。不一定存在，如果存在的话，用|隔开
		 * 9，membership：会员级别。默认为0
		 * 10，device_id：设备ID。用户当前的设备id
		 * 11，timestamp2当时的时间戳。用于判断是否过期
 *	注意：
 * 		1、token有效是timestamp2 + expires_in小于当前时间戳。如果无效，则抛出登录失效的错误
 * 		2、暂时使用AES加密，如果实际中这个公共模块太大，则改用base64处理。
 * 2，登录前没有token。
 * 3、登录时生成token。
 * 4、每次请求，有原token的情况下才更新，没有原token不执行。
 * 6、对应的云对象的_before将会有对这个token的验证
 */
module.exports = {
	/**
	 * 生成token
	 * 只有登录的时候才调用，其它场景都是更新
	 */
	get,
	
	/**
	 * 解析Token，并验证和更新最新的token。
	 * 
	 * 1、验证合法性，总共十项；
	 * 2、验证应用类型；是否包含当前用户应用
	 * 3、验证甲方；
	 * 4、验证项目；
	 * 5、验证级别。排除前两位字母，比较后面的数字，越小级别越大
	 * 6、验证权限。权限有重叠即可
	 * 7、验证会员。会员权限大于等于即可
	 * 8、验证用户。表示特定用户才允许访问
	 * 9、验证上次请求是否过期。过期了没关系。
	 * 10、验证登录时间是否过期。只有在已过期得到情况下才验证登录是否过期
	 * 
	 * demand：验证要求。value是字符串或者数组。最好是数组（rank除外）
	 * 		is = true： 是否要验证token。不存在就不通过
	 * 		apply：验证应用类型。如果存在就验证
	 * 		partya：验证甲方
	 * 		target：验证项目
	 * 		rank：验证级别
	 * 		authority：验证权限
	 * 		membership：验证会员级别
	 * 		user：验证用户
	 */
	verify(token, demand, success, fail) {
		const {
			current = Date.now(),
			is = true, // 是否要验证token。不存在就不通过
			apply, // 验证应用类型。如果存在就验证
			partya, // 验证甲方
			target, // 验证项目
			rank, // 验证用户级别。排除前两位字母，比较后面的数字，越小级别越大
			authority, // 验证用户权限。只要用户权限与要求权限有重叠的就满足
			membership, // 验证会员级别
			user, // 验证用户。限定用户访问的云对象
			callback, // 回调验证。返回code和message。code === 0表示通过
			onlyAnalyse // 不做任何验证，直接带解析结果返回
		} = demand || {}
		if (is) {
			if (token) {
				const arr = analysis(token)
				// 验证通过
				const lastAction = () => {
					arr[10] = current
					// 项目代码和前缀
					const a = arr[4].split('-')
					// 验证通过回调的信息
					const res = {
						// 登录时间
						Login: parseInt(arr[0]),
						// 当前应用类别
						Apply: arr[1], 
						// 是否测试环境。测试环境所有的数据库都将是测试数据库
						Test: arr[2].indexOf('test') > -1,
						// 是否开发环境。开发环境下有的接口处理会不一样
						Development: arr[2].indexOf('develop') > -1, 
						// 用户id
						User: arr[3], 
						// 当前项目
						Target: a[0],
						// 当前项目的前缀
						Prefix: a.length === 2 ? a[1] : '',
						// 当前甲方
						Partya: arr[5],
						// 当前级别
						Rank: arr[6],
						// 当前权限
						Authority: arr[7],
						// 当前会员级别
						Membership: arr[8],
						// 当前设备
						Device: arr[9],
						// 返回最新的Token
						Token: get(arr)
					}
					// 验证不通过回调的信息
					const error = errorInfo[10]
					if (callback) {
						const obj = callback(res)
						if (obj.code) {
							error.code = obj.code
						}
						if (obj.message) {
							error.message = obj.message
						}
					} else {
						// 验证通过
						error.code = 0
					}
					if (error.code === 0) {
						// 回调解析出来的信息
						success && success(res)
					} else {
						fail && fail(error)
					}
				}
				if (onlyAnalyse) {
					lastAction()
				} else {
					// 1，验证合法性，总共十一项
					if (arr.length === 11) {
						// 2，验证应用
						if (!apply || apply.indexOf(arr[2]) > -1) {
							// 3，验证甲方
							if (!partya || partya === arr[5]) {
								// 4，验证项目
								if (!target || target === arr[4]) {
									// 5，验证级别
									let bool5 = !rank
									if (rank) {
										let pre1 = rank.substr(0, 2)
										let pre2 = arr[6].substr(0, 2)
										if (pre1 === pre2) { // 前两位要相同，后面的数值小才满足
											if (parseInt(arr[6].substr(2)) < parseInt(rank.substr(2))) {
												bool5 = true
											}
										}
									}
									if (bool5) {
										// 6，权限验证
										let bool6 = !authority
										if (authority) {
											if (arr[7]) { // 判断权限是否有重叠
												const temparr = arr[7].split('|')
												for (let i = 0; i < temparr.length; i++) {
													if (authority.indexOf(temparr[i]) > -1) {
														bool6 = true
														break
													}
												}
											}
										}
										if (bool6) {
											// 7，验证会员
											if (!membership || arr[8] > membership) {
												// 7，验证用户
												if (!user || user.indexOf(arr[3]) > -1) {
													// 8，验证上次请求是否过期
													if (parseInt(arr[10]) + expires_in < current) {
														lastAction()
													} else {
														// 9，验证登录时间是否过期。只有在已过期的情况下才验证登录是否过期
														if (arr[0] + expires_login > current) {
															if (0) {
																// 不执行请求已过期
																fail && fail(errorInfo[8])
															} else {
																lastAction()
															}
														} else {
															fail && fail(errorInfo[9])
														}
													}
												} else {
													fail && fail(errorInfo[7])
												}
											} else {
												fail && fail(errorInfo[11])
											}
										} else {
											fail && fail(errorInfo[6])
										}
									} else {
										fail && fail(errorInfo[5])
									}
								} else {
									fail && fail(errorInfo[4])
								}
							} else {
								fail && fail(errorInfo[3])
							}
						} else {
							fail && fail(errorInfo[2])
						}
					} else {
						fail && fail(errorInfo[1])
					}
				}
			} else {
				if (onlyAnalyse) {
					success && success()
				} else {
					fail && fail(errorInfo[1])
				}
			}
		} else {
			success && success()
		}
	}
}